﻿using System.Collections.Generic;
using Deferred.Library.Scene;
using Deferred.Library.Scene.Visual;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Deferred.Library.Deferred
{
    class PointLightRenderEngine : GraphicalComponent
    {
        private DualContentManager content;
        private Effect drawLightDebugEffect;
        private Effect pointLightEffect;
        private Model pointLightSphere;

        private EffectParameter cameraPositionParameter;
        private EffectParameter inverseViewProjParameter;
        private EffectParameter lightIntensityParameter;
        private EffectParameter lightColorParameter;
        private EffectParameter lightPositionParameter;
        private EffectParameter worldParameter;
        private EffectParameter lightRadiusParameter;
        private EffectParameter debugWorldParameter;
        private EffectParameter debugLightColorParameter;

        public PointLightRenderEngine(DeferredBase deferredBase)
            : base(deferredBase.DeviceService)
        {
            this.content = deferredBase.Content;
        }

        public void Initialize()
        {
            drawLightDebugEffect = content.InternalContent.Load<Effect>("DrawLightDebug");
            pointLightEffect = content.InternalContent.Load<Effect>("PointLight");
            pointLightSphere = content.InternalContent.Load<Model>("sphere");

            cameraPositionParameter = pointLightEffect.Parameters["cameraPosition"];
            inverseViewProjParameter = pointLightEffect.Parameters["InverseViewProj"];
            lightIntensityParameter = pointLightEffect.Parameters["lightIntensity"];
            lightColorParameter = pointLightEffect.Parameters["lightColor"];
            lightPositionParameter = pointLightEffect.Parameters["lightPosition"];
            worldParameter = pointLightEffect.Parameters["World"];
            lightRadiusParameter = pointLightEffect.Parameters["lightRadius"];
            debugWorldParameter = drawLightDebugEffect.Parameters["World"];
            debugLightColorParameter = drawLightDebugEffect.Parameters["lightColor"];
        }

        public void drawPointLights(IList<PointLight> lights, CameraVisual camera)
        {
            cameraPositionParameter.SetValue(camera.Position);
            inverseViewProjParameter.SetValue(Matrix.Invert(camera.View * camera.Projection));

            foreach (PointLight light in lights)
            {
                DrawPointLight(light, camera);
            }
        }

        private void DrawPointLight(PointLight light, CameraVisual camera)
        {
            lightIntensityParameter.SetValue(light.Intensity);
            lightColorParameter.SetValue(light.Color.ToVector3());
            lightPositionParameter.SetValue(light.Position);
            worldParameter.SetValue(light.World);
            lightRadiusParameter.SetValue(light.Radius);

            if (isCameraInSphere(light, camera))
            {
                GraphicsDevice.RenderState.CullMode = CullMode.CullClockwiseFace;
            }
            else
            {
                GraphicsDevice.RenderState.CullMode = CullMode.CullCounterClockwiseFace;
            }

            pointLightEffect.Begin();
            foreach (EffectPass pass in pointLightEffect.Techniques["PointLight"].Passes)
            {
                pass.Begin();
                foreach (ModelMesh mesh in pointLightSphere.Meshes)
                {
                    foreach (ModelMeshPart part in mesh.MeshParts)
                    {
                        GraphicsDevice.VertexDeclaration = part.VertexDeclaration;
                        GraphicsDevice.Vertices[0].SetSource(mesh.VertexBuffer, part.StreamOffset, part.VertexStride);
                        GraphicsDevice.Indices = mesh.IndexBuffer;
                        GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, part.BaseVertex, 0, part.NumVertices, part.StartIndex, part.PrimitiveCount);
                    }
                }
                pass.End();
            }
            pointLightEffect.End();

            GraphicsDevice.RenderState.CullMode = CullMode.CullCounterClockwiseFace;
        }

        public void DrawPointLightsDebug(IList<PointLight> lights)
        {
            foreach (PointLight light in lights)
            {
                DrawPointLightDebug(light);
            }
        }

        private void DrawPointLightDebug(PointLight light)
        {
            debugWorldParameter.SetValue(light.World);
            debugLightColorParameter.SetValue(light.Color.ToVector4());
            foreach (EffectPass pass in drawLightDebugEffect.Techniques["DrawLightDebug"].Passes)
            {
                pass.Begin();
                foreach (ModelMesh mesh in pointLightSphere.Meshes)
                {
                    foreach (ModelMeshPart part in mesh.MeshParts)
                    {
                        GraphicsDevice.VertexDeclaration = part.VertexDeclaration;
                        GraphicsDevice.Vertices[0].SetSource(mesh.VertexBuffer, part.StreamOffset, part.VertexStride);
                        GraphicsDevice.Indices = mesh.IndexBuffer;
                        GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, part.BaseVertex, 0, part.NumVertices, part.StartIndex, part.PrimitiveCount);
                    }
                }
                pass.End();
            }
        }

        private bool isCameraInSphere(PointLight light, CameraVisual camera)
        {
            return Vector3.Distance(camera.Position, light.Position) <= light.Radius;
        }
    }
}
